|
1
|
|
|
var Race = require('./Race').Race |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* @callback Future~resolver |
|
5
|
|
|
* |
|
6
|
|
|
* @param {Function} resolve |
|
7
|
|
|
* @param {Function} reject |
|
8
|
|
|
*/ |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* @enum |
|
12
|
|
|
* @readonly |
|
13
|
|
|
*/ |
|
14
|
|
|
var Status = { |
|
15
|
|
|
Pending: 0, |
|
16
|
|
|
Resolving: 1, |
|
17
|
|
|
Rejected: 2, |
|
18
|
|
|
Fulfilled: 3 |
|
19
|
|
|
} |
|
20
|
|
|
|
|
21
|
|
|
Status.name = function (value) { |
|
22
|
|
|
return Object.keys(Status).reduce(function (carrier, key) { |
|
23
|
|
|
return carrier || (Status[key] === value ? key : null) |
|
24
|
|
|
}, null) |
|
25
|
|
|
} |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* This is a very standard promise interface implementation, but with ability |
|
29
|
|
|
* to be externally completed or cancelled by `#resolve()` and `#reject()` |
|
30
|
|
|
* methods. |
|
31
|
|
|
* |
|
32
|
|
|
* It is named after Java's CompletableFuture. |
|
33
|
|
|
* |
|
34
|
|
|
* **NB:** this implementation doesn't account for cyclic references (which are |
|
35
|
|
|
* tremendously easy to implement). |
|
36
|
|
|
* |
|
37
|
|
|
* @class |
|
38
|
|
|
* @template T |
|
39
|
|
|
* |
|
40
|
|
|
* @param {Future~resolver} [resolver] Standard promise resolver |
|
41
|
|
|
*/ |
|
42
|
|
|
function Future (resolver) { |
|
43
|
|
|
var self = this |
|
44
|
|
|
var status = Status.Pending |
|
45
|
|
|
var identity |
|
46
|
|
|
var propagation = null |
|
47
|
|
|
var queue = [] |
|
48
|
|
|
|
|
49
|
|
|
this.getValue = function () { return identity } |
|
50
|
|
|
|
|
51
|
|
|
this.getStatus = function () { return status } |
|
52
|
|
|
|
|
53
|
|
|
this.hasStatus = function (s) { return status === s } |
|
54
|
|
|
|
|
55
|
|
|
this.isPending = function () { return self.hasStatus(Status.Pending) } |
|
56
|
|
|
this.isFulfilled = function () { return self.hasStatus(Status.Fulfilled) } |
|
57
|
|
|
this.isRejected = function () { return self.hasStatus(Status.Rejected) } |
|
58
|
|
|
this.isResolved = function () { |
|
59
|
|
|
return status === Status.Fulfilled || status === Status.Rejected |
|
60
|
|
|
} |
|
61
|
|
|
|
|
62
|
|
|
function schedulePropagation () { |
|
63
|
|
|
// if already scheduled |
|
64
|
|
|
if (propagation) { return } |
|
65
|
|
|
// @see https://promisesaplus.com/#point-34 |
|
66
|
|
|
// setTimeout is used because process.nextTick is not something one mocks |
|
67
|
|
|
propagation = setTimeout(function () { |
|
68
|
|
|
propagation = null |
|
69
|
|
|
propagate() |
|
70
|
|
|
}, 0) |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
function propagate () { |
|
74
|
|
|
if (!self.isResolved()) { return } |
|
75
|
|
|
var staged = queue |
|
76
|
|
|
queue = [] |
|
77
|
|
|
staged.forEach(function (callback) { |
|
78
|
|
|
callback(status, identity) |
|
79
|
|
|
}) |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
function setIdentity (nextStatus, value) { |
|
83
|
|
|
if (self.isResolved()) { return } |
|
84
|
|
|
status = nextStatus |
|
85
|
|
|
identity = value |
|
86
|
|
|
schedulePropagation() |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
|
|
var fulfill = setIdentity.bind(self, Status.Fulfilled) |
|
|
|
|
|
|
90
|
|
|
var reject = setIdentity.bind(self, Status.Rejected) |
|
|
|
|
|
|
91
|
|
|
|
|
92
|
|
|
function resolve (nextStatus, value) { |
|
93
|
|
|
if (!self.isPending()) { return self } |
|
94
|
|
|
extendedResolutionProcedure(nextStatus, value) |
|
95
|
|
|
return self |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
/** |
|
99
|
|
|
* Resolves (fulfills) current instance with provided value |
|
100
|
|
|
* |
|
101
|
|
|
* @param {T} [value] |
|
102
|
|
|
* @returns {Future.<T>} Current instance |
|
103
|
|
|
*/ |
|
104
|
|
|
this.fulfill = resolve.bind(this, Status.Fulfilled) |
|
|
|
|
|
|
105
|
|
|
|
|
106
|
|
|
this.resolve = this.fulfill |
|
107
|
|
|
|
|
108
|
|
|
/** |
|
109
|
|
|
* Rejects current instance with provided value |
|
110
|
|
|
* |
|
111
|
|
|
* @param {T} [value] |
|
112
|
|
|
* @returns {Future.<T>} Current instance |
|
113
|
|
|
*/ |
|
114
|
|
|
this.reject = resolve.bind(this, Status.Rejected) |
|
|
|
|
|
|
115
|
|
|
|
|
116
|
|
|
if (resolver) { resolver(this.resolve, this.reject) } |
|
117
|
|
|
|
|
118
|
|
|
/** |
|
119
|
|
|
* @param x |
|
120
|
|
|
*/ |
|
121
|
|
|
function promiseResolutionProcedure (x) { |
|
122
|
|
|
if (self.isResolved()) { return } |
|
123
|
|
|
status = Status.Resolving |
|
124
|
|
|
if (x === self) { |
|
125
|
|
|
var message = 'Can\'t resolve promise with itself' |
|
126
|
|
|
return setIdentity(Status.Rejected, new TypeError(message)) |
|
127
|
|
|
} |
|
128
|
|
|
if (x instanceof Future && x.isResolved()) { |
|
129
|
|
|
return extendedResolutionProcedure(x.getStatus(), x.getValue()) |
|
130
|
|
|
} |
|
131
|
|
|
if (!x || (typeof x !== 'function' && typeof x !== 'object')) { |
|
132
|
|
|
return fulfill(x) |
|
133
|
|
|
} |
|
134
|
|
|
var race = new Race(1) |
|
135
|
|
|
var resolvePromise = race.racer(promiseResolutionProcedure) |
|
136
|
|
|
var rejectPromise = race.racer(reject) |
|
137
|
|
|
try { |
|
138
|
|
|
var then = x.then |
|
139
|
|
|
if (typeof then !== 'function') { return fulfill(x) } |
|
140
|
|
|
then.call(x, resolvePromise, rejectPromise) |
|
|
|
|
|
|
141
|
|
|
} catch (e) { |
|
142
|
|
|
rejectPromise(e) |
|
|
|
|
|
|
143
|
|
|
} |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
function extendedResolutionProcedure (status, x) { |
|
147
|
|
|
status === Status.Rejected ? reject(x) : promiseResolutionProcedure(x) |
|
148
|
|
|
return self |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
|
|
/** |
|
152
|
|
|
* Standard then-implementation |
|
153
|
|
|
* |
|
154
|
|
|
* @param {Function} [onFulfill] |
|
155
|
|
|
* @param {Function} [onReject] |
|
156
|
|
|
* @returns {Future.<T>} |
|
157
|
|
|
*/ |
|
158
|
|
|
this.then = function (onFulfill, onReject) { |
|
159
|
|
|
if (typeof onFulfill !== 'function') { onFulfill = null } |
|
160
|
|
|
if (typeof onReject !== 'function') { onReject = null } |
|
161
|
|
|
if (!onFulfill && !onReject) { return self } |
|
162
|
|
|
onReject = onReject || function (error) { throw error } |
|
163
|
|
|
onFulfill = onFulfill || function (value) { return value } |
|
164
|
|
|
var target = new Future() |
|
165
|
|
|
var callback = function (status, value) { |
|
166
|
|
|
var handler = status === Status.Fulfilled ? onFulfill : onReject |
|
167
|
|
|
try { |
|
168
|
|
|
target.resolve(handler(value)) |
|
169
|
|
|
} catch (e) { |
|
170
|
|
|
target.reject(e) |
|
171
|
|
|
} |
|
172
|
|
|
} |
|
173
|
|
|
queue.push(callback) |
|
174
|
|
|
schedulePropagation() |
|
175
|
|
|
return target |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
this.toString = function () { |
|
179
|
|
|
var state = Status.name(status) |
|
180
|
|
|
return 'Future <' + state + (identity ? ':' + identity : '') + '>' |
|
181
|
|
|
} |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
/** |
|
185
|
|
|
* Returns promise that awaits all passed promises. |
|
186
|
|
|
* |
|
187
|
|
|
* @param {Array.<Promise.<*>|Thenable<*>>} promises |
|
188
|
|
|
* @returns {Future.<*>} |
|
189
|
|
|
*/ |
|
190
|
|
|
Future.all = function (promises) { |
|
191
|
|
|
return Future.wrap(Promise.all(promises)) |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
/** |
|
195
|
|
|
* Returns result of first resolved promise, be it fulfillment or rejection |
|
196
|
|
|
* |
|
197
|
|
|
* @param {Array.<Promise.<*>|Thenable<*>>} promises |
|
198
|
|
|
* @returns {Future.<*>} |
|
199
|
|
|
*/ |
|
200
|
|
|
Future.race = function (promises) { |
|
201
|
|
|
return Future.wrap(Promise.race(promises)) |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* Returns resolved promise. |
|
206
|
|
|
* |
|
207
|
|
|
* @param {*} [value] |
|
208
|
|
|
* @returns {Future.<*>} |
|
209
|
|
|
*/ |
|
210
|
|
|
Future.resolve = function (value) { |
|
211
|
|
|
return new Future().resolve(value) |
|
212
|
|
|
} |
|
213
|
|
|
|
|
214
|
|
|
/** |
|
215
|
|
|
* Returns rejected promise. |
|
216
|
|
|
* |
|
217
|
|
|
* @param {*} [value] |
|
218
|
|
|
* @returns {Future.<*>} |
|
219
|
|
|
*/ |
|
220
|
|
|
Future.reject = function (value) { |
|
221
|
|
|
return new Future().reject(value) |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
/** |
|
225
|
|
|
* Wraps given promise in a Future, giving user code an option |
|
226
|
|
|
* to reject/resolve it. |
|
227
|
|
|
* |
|
228
|
|
|
* @param {Promise.<*>|Thenable.<*>} promise |
|
229
|
|
|
* @returns {Future.<*>} |
|
230
|
|
|
*/ |
|
231
|
|
|
Future.wrap = function (promise) { |
|
232
|
|
|
return new Future(function (resolve, reject) { |
|
233
|
|
|
// silencing unhandled promise rejection |
|
234
|
|
|
promise.then(resolve, reject).then(null, function () {}) |
|
235
|
|
|
}) |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
Future.Status = Status |
|
239
|
|
|
|
|
240
|
|
|
module.exports = { |
|
241
|
|
|
Future: Future |
|
242
|
|
|
} |
|
243
|
|
|
|